package com.zl.zsk.filter;

import com.google.code.kaptcha.Constants;
import com.zl.zsk.config.security.JwtProvider;
import com.zl.zsk.constant.RedisKeys;
import com.zl.zsk.constant.RetCode;
import com.zl.zsk.exception.BizException;
import com.zl.zsk.exception.ImageCodeException;
import com.zl.zsk.exception.JwtInvalidException;
import com.zl.zsk.exception.NotLoginException;
import com.zl.zsk.pojo.SysUser;
import com.zl.zsk.util.RedisUtil;
import io.swagger.annotations.ApiModelProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.file.NotLinkException;
import java.util.Arrays;

/**
 * @author lxb
 * @version 1.0.0
 * @desc:
 * @date 2020-03-13 15:40
 */
public class SecurityFilter extends OncePerRequestFilter {
    private final Logger log = LoggerFactory.getLogger(SecurityFilter.class);

    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;

    @Autowired
    private JwtProvider jwtProvider;

    @Autowired
    private UserDetailsService userService;

    @Autowired
    private RedisUtil redisUtil;

    private final String[] allowNoToken = {"/user/login", "/user/code/image"};

    public boolean validLogin(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        if (request.getRequestURI().contains("/login")) {
            String validCode = request.getParameter("validCode");
            if (StringUtils.isEmpty(validCode)) {
                authenticationFailureHandler.onAuthenticationFailure(request, response, new ImageCodeException("请输入验证码"));
                return false;
            }
            //获取系统生成的验证码
            String sysValidCode = (String) request.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY);
            if (sysValidCode == null) {
                log.info("服务重启了吗? 找不到存放于session中的验证码");
                sysValidCode = "";
            }
            if (!validCode.trim().equals(sysValidCode.trim())) {
                authenticationFailureHandler.onAuthenticationFailure(request, response, new ImageCodeException("验证码不正确"));
                return false;
            }
        }
        return true;
    }

    public void setAuth(String token, HttpServletRequest request){
        boolean isValid;
        try {
            isValid = jwtProvider.validateJwtToken(token);
        } catch (Exception e) {
            isValid = false;
        }
        if (!isValid) {
            log.error("token 验证失败:{}",token);
            throw new JwtInvalidException(RetCode.JWT_TOKEN_ERROR.msg);
        }
//        String username = jwtProvider.getUserNameFromJwtToken(token);
        SysUser sysUser = null;
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        //如果为null说明已经退出或者没有登录过，应该是要求重新登录会更好而不是直接查询数据库，->
        //->否则退出后,直接再请求别的如用户列表页，可以直接获取结果，而不是让用户重新登录
        if (authentication == null) {
            String redisKey = String.format(RedisKeys.USER_TOKEN, token);
            if (!redisUtil.hasKey(redisKey)) {
                    //请登录后再操作
//                authenticationFailureHandler.onAuthenticationFailure(request, response,new NotLoginException(""));
                throw new NotLoginException(RetCode.NOTLOGIN.msg);
//                sysUser = (SysUser) userService.loadUserByUsername(username);
//                redisUtil.set(redisKey, sysUser, RedisKeys.HALF_HOUR);
            } else {
                sysUser = (SysUser) redisUtil.get(redisKey);
            }
        }
        UsernamePasswordAuthenticationToken authenticationToken
                = new UsernamePasswordAuthenticationToken(sysUser, null, sysUser.getAuthorities());
        authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);


    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//        boolean b = validLogin(request, response);
//        if (!b) {
//            return;
//        }
        //是否不需要验证token的请求
        boolean b = Arrays.stream(allowNoToken).anyMatch((s) -> request.getRequestURI().contains(s));

        if (!b) {//进入这里说明b = false 即需要token来验证的
            String token = getJwt(request);
            log.info("获取的jwt信息：{}", token);
            try{
                setAuth(token, request);
            }catch (AuthenticationException e){
                authenticationFailureHandler.onAuthenticationFailure(request, response, e);
                return;
            }
        }else {//进入这里说明b = true 即不需要进行验证token
            if(!validLogin(request,response)){
                return;
            }
        }
        filterChain.doFilter(request, response);
    }


    /**
     * 从请求头中获取token字段的值，即jwt的信息
     *
     * @param request
     * @return
     */
    private String getJwt(HttpServletRequest request) {
        String authHeader = request.getHeader("token");
        if (StringUtils.isEmpty(authHeader)) {
            return null;
        }
        return authHeader;
    }
}
